14 读取Excel文件
14.1 引言Excel在金融数据交换中的地位
尽管Python和数据库在金融分析中日益重要,Excel仍然是:
- 数据交换: 数据源和报告的标准格式
- 人工输入: 交易员和分析师的常用工具
- 遗留系统: 许多老系统仍使用Excel
14.2 本章学习目标
通过本章学习,你将能够:
- 使用
pd.read_excel的sheet_name、skiprows、usecols、nrows参数精确读取指定工作表、数据区与行列范围 - 解释
converters与dtype的差别,并用converters配合自定义函数在读取时清洗特殊标记 - 使用
pd.ExcelFile上下文管理器一次打开文件、多次读取多个工作表 - 根据参数与适用场景,比较读取 Excel 与读取 CSV 的异同
先修内容:第 章节 10 章至第 章节 13 章的数据框操作基础。
数据说明:本章使用的 stores.xlsx 为教学平台内置数据文件,本地仓库不包含(相关代码块已注明),读取代码请在教学平台上运行。按本章用法,读取时统一指定 sheet_name='2019'(或 '2020')、skiprows=1(跳过数据区上方的说明行)、usecols='B:F'(只取 B 至 F 列)。其中 Flagship 列在本章只作为演示”读取即清洗”的对象列使用,不额外展开业务口径;其取值中存在空字符串与 'MISSING' 两种缺失标记。按平台代码注释的语义,fix_missing(x) 在 x 为空字符串或 'MISSING' 时返回 False,其余取值原样返回,因此经 converters={'Flagship': fix_missing} 转换后,缺失标记被统一映射为 False。
14.3 read_excel函数
# 注:stores.xlsx数据文件本地没有,但平台已经内置
# =============================================================================
# 题目:使用read_excel读取Excel文件
# =============================================================================
# 本示例演示如何从Excel文件读取特定工作表和数据范围
# 金融应用:读取交易所提供的股票行情数据、财务报表Excel文件等
# ==================== 导入库 ====================
import pandas as pd # 导入Pandas库,用于读取和处理Excel数据
# ==================== 基础读取Excel ====================
# 读取Excel文件的特定工作表和数据范围
df = pd.read_excel(
"stores.xlsx", # Excel文件路径(可以是相对路径或绝对路径)
sheet_name="2019", # 指定工作表名称(可以是名称字符串或索引数字)
skiprows=1, # 跳过前1行(常用于跳过标题行或说明行)
usecols="B:F" # 只读取B列到F列(可用字母范围或列名列表)
)
# 返回:包含读取数据的DataFrame对象
print("数据框信息:") # 打印提示信息
print(df.info()) # 显示DataFrame的详细信息(列名、数据类型、非空值数量等)参数详解:
sheet_name: 工作表名或索引skiprows: 跳过的行数usecols: 读取的列(字母或列名列表)dtype: 指定列的数据类型converters: 列转换函数字典
14.4 数据类型转换
任务要求:分三步读取 stores.xlsx:先按 sheet_name='2019'、skiprows=1、usecols='B:F' 读入 df 并输出 df.info();再定义 fix_missing 函数,以 converters={'Flagship': fix_missing} 重新读入 df2,输出 df2.info() 与整张 df2;最后用 pd.ExcelFile 上下文管理器一次打开文件,分别读取 2019、2020 两个工作表的前 2 行并输出 data1。请将代码原样输入教学平台(注释除外),判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注:stores.xlsx数据文件本地没有,但平台已经内置
import pandas as pd # 导入Pandas数据分析库
df = pd.read_excel("stores.xlsx",sheet_name="2019", skiprows=1, usecols="B:F") # 从Excel文件读取数据存入df
print(df.info()) # 输出数据框基本信息
def fix_missing(x): # 定义函数fix_missing
return False if x in ["", "MISSING"] else x # 返回计算结果
# 从Excel文件读取数据存入df2
df2 = pd.read_excel("stores.xlsx",sheet_name="2019", skiprows=1, usecols="B:F",converters={"Flagship": fix_missing})
print(df2.info()) # 输出数据框基本信息
print(df2) # 输出数据框数据
with pd.ExcelFile("stores.xlsx") as f: # 使用上下文管理器
# 从Excel文件读取数据存入data1
data1 = pd.read_excel(f, "2019", skiprows=1, usecols="B:F", nrows=2,converters={"Flagship": fix_missing})
# 从Excel文件读取数据存入data2
data2 = pd.read_excel(f, "2020", skiprows=1, usecols="B:F", nrows=2,converters={"Flagship": fix_missing})
print(data1) # 输出数据数据预期输出(数据文件为教学平台内置,本机无法复现,判读要点如下;具体数值以平台运行结果为准):
- 先看第一段
df.info():留意Flagship列的dtype与非空计数(non-null)——该列含有特殊标记,读取行为与普通列不同 - 再对比第二段
df2.info()中Flagship列的变化:converters在读取时逐单元格调用fix_missing,把空字符串与'MISSING'映射为False;print(df2)随后输出整张清洗后的表 - 最后输出 2019 年工作表前 2 行的
data1(nrows=2);若在本地复现后续代码块,需先运行本代码块以使fix_missing已定义
14.5 ExcelFile类高效读取多表
# 注:stores.xlsx数据文件本地没有,且fix_missing函数定义于上方平台任务代码块中
# =============================================================================
# 题目:使用ExcelFile类高效读取多个工作表
# =============================================================================
# 本示例演示使用ExcelFile类一次性打开文件并读取多个工作表
# 金融应用:读取同一Excel文件中的多年财务数据、多个子公司的报表等
# ==================== 使用with语句打开Excel文件 ====================
# 使用ExcelFile类打开文件(提高多次读取效率)
with pd.ExcelFile("stores.xlsx") as f:
# with语句确保文件在使用后自动关闭,释放系统资源
# f是ExcelFile对象,代表已打开的Excel文件
# ==================== 读取2019年数据 ====================
# 读取2019年工作表的前2行数据
data1 = pd.read_excel(
f, # 传入已打开的ExcelFile对象,而非文件路径
"2019", # 工作表名称
skiprows=1, # 跳过第1行
usecols="B:F", # 读取B到F列
nrows=2, # 只读取前2行数据(用于快速预览或测试)
converters={"Flagship": fix_missing} # 应用转换函数
)
# nrows参数常用于:数据预览、限制读取量、测试数据格式等
# ==================== 读取2020年数据 ====================
# 读取2020年工作表的前2行数据
data2 = pd.read_excel(
f, # 复用同一个ExcelFile对象,避免重复打开文件
"2020", # 工作表名称
skiprows=1, # 跳过第1行
usecols="B:F", # 读取B到F列
nrows=2, # 只读取前2行
converters={"Flagship": fix_missing} # 应用转换函数
)
# ==================== 显示读取结果 ====================
print("2019年前2行:") # 打印提示信息
print(data1) # 显示2019年数据
print(f"\n2020年前2行:") # 打印提示信息(带换行)
print(data2) # 显示2020年数据优势:
- 只打开文件一次
- 适合读取多个工作表
- 自动管理文件句柄
14.6 本章小结
要点:
sheet_name选工作表,skiprows跳过说明行,usecols圈定列区,nrows限制读取行数converters在读取时逐单元格调用函数,适合清洗空字符串与'MISSING'这类特殊标记pd.ExcelFile配合with只打开一次文件即可读取多个工作表,并自动管理文件句柄- 复用其他代码块定义的函数(如
fix_missing)时,先运行那个代码块
易错点:
usecols='B:F'(按列位置)与usecols=['列名', ...](按列名)两种写法依据不同,混用易错位skiprows=1跳过的是数据区上方的行,若表头不止一行,读到的列名会不对- 忘记先定义
fix_missing就运行使用它的代码块,会抛出NameError
14.7 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
import pandas as pd def fix_missing(x): return False if x in ['', 'MISSING'] else x df = pd.DataFrame({'Flagship': [True, '', 'MISSING', False]}) print(df['Flagship'].apply(fix_missing))参考答案(先写下你的预测再点开)
解题思路:
apply把fix_missing逐个作用到列的每个元素上:True不在['', 'MISSING']中,原样返回True;空字符串''命中列表,返回False;'MISSING'命中列表,返回False;False不在列表中,原样返回False。四个返回值恰好都是布尔值,pandas 因此把整列推断为bool类型(而不是 object)。另要注意:空字符串''是真实存在的字符串值,与缺失值NaN不同——'' in ['', 'MISSING']命中,而NaN参与比较既不等于''也不等于'MISSING',会走else分支原样返回。# 验证脚本:观察fix_missing逐元素清洗后的取值与类型 import pandas as pd # 导入Pandas库 def fix_missing(x): # 与题面相同的清洗函数 return False if x in ['', 'MISSING'] else x df = pd.DataFrame({'Flagship': [True, '', 'MISSING', False]}) # 含两种缺失标记的Flagship列 print(df['Flagship'].apply(fix_missing)) # 逐元素清洗,缺失标记统一映射为False预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
0 True 1 False 2 False 3 False Name: Flagship, dtype: bool回扣本章:对应本章小结“要点”第 2 条——
converters(以及等价的apply后清洗)在读取时逐单元格调用函数,适合清洗空字符串与'MISSING'这类特殊标记。概念辨析:
converters与dtype分别在读取的哪个环节起作用?为什么含'MISSING'标记的列不能直接声明dtype=bool?pd.read_excel与pd.ExcelFile各适合什么场景?参考答案(点开前请先独立完成)
解题思路:逐点作答。第一,环节不同:
converters作用在“读取解析”环节——每读到一个单元格就调用一次指定函数,先转换后成列,可以把''、'MISSING'这类特殊标记在读入时就映射成合法取值;dtype作用在“成列定类型”环节——整列解析完成后强制声明类型,不做任何清洗,遇到无法映射的值直接抛异常。第二,含'MISSING'的列不能声明dtype=bool:布尔类型转换要求每个值本身可映射为True/False(如 0/1、'True'/'False'),'MISSING'无法映射,会抛ValueError;正确顺序是先用converters={'Flagship': fix_missing}把标记清洗成False,清洗后整列取值自然成为布尔,类型不需要再强制。第三,分工:pd.read_excel每次调用都会完整打开并解析一次文件,读单表最直接;pd.ExcelFile配合with只打开一次文件、句柄可复用,同一文件的多个工作表或多次读取共享一次解析开销,并自动管理文件句柄。回扣本章:对应本章小结“要点”第 2、3 条与“易错点”第 3 条——
converters先清洗、dtype后定类型;pd.ExcelFile适合一次打开、读多张工作表。变式任务(平台数据):在教学平台上用
pd.ExcelFile一次打开stores.xlsx,分别读取 2019 与 2020 两个工作表的全部数据,比较两年门店数量的变化。参考答案(点开前请先独立完成)
解题思路:改造 列表 14.2 第三步的读取语句。第一步,把
with pd.ExcelFile('stores.xlsx') as f:中的两次读取各去掉nrows=2(读全部数据而非前 2 行),保留skiprows=1(跳过数据区上方的说明行)与usecols='B:F',分别存入df_2019、df_2020两个数据框;converters={'Flagship': fix_missing}可保留,也可视比较目标省去。第二步,比较两年门店数量:若每行对应一家门店,直接对比df_2019.shape[0]与df_2020.shape[0];若数据含门店编号列,更稳妥的是对比nunique()(唯一门店数),可避免重复行干扰。若想同时看增减方向,可用pd.concat([df_2019.assign(年份=2019), df_2020.assign(年份=2020)])拼接后groupby('年份').size()一并汇总。结构性判读:输出应为两个整数(两年各自的门店行数)或一张按年份汇总的行数表。判读要点看三件事:两年行数是否相等(不等说明有门店进出);差异的方向与幅度(净增还是净减、幅度是否与业务背景相称);用唯一门店数复核行数差异是否由重复记录造成。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的思路,列表 14.2 对应的平台原始代码仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应本章小结“要点”第 1、3 条——
skiprows/usecols/nrows圈定读取范围,pd.ExcelFile一次打开即可读取多个工作表。动手验证:本地用
to_excel自建一个含两个工作表的小文件,再分别用sheet_name=0、sheet_name=1、nrows=2、skiprows=1读取,逐一观察参数对读取结果的影响。参考答案(点开前请先独立完成)
解题思路:先自建 4 行小表并用
ExcelWriter写出两个工作表,且仿照正文中stores.xlsx的布局用startrow=1把数据区整体下移一行(第 1 行留空)。然后四个参数逐一观察:sheet_name=0与sheet_name=1按位置选表,0 是第 1 张(Sheet1)、1 是第 2 张(Sheet2);nrows=2只读表头之后的前 2 行数据;skiprows=1跳过文件最上方的 1 行——本例文件首行是空行,跳过它之后真正的表头才会被识别。对照最后一行输出:不写skiprows时,空行被当作表头,列名全部变成Unnamed: 0、Unnamed: 1,真正的表头被挤成第一行数据,这正是正文强调skiprows=1用途的原因。# 验证脚本:自建双工作表文件并观察四个读取参数的效果 import pandas as pd # 导入Pandas库 demo_df = pd.DataFrame({'代码': ['600000.SH', '600036.SH', '601318.SH', '600519.SH'], '收盘价': [10.5, 45.2, 55.8, 1700.0]}, index=pd.date_range('2024-01-01', periods=4)) # 自建4行小表 demo_df.index.name = '交易日' # 给行索引命名,写出的首列才有表头 with pd.ExcelWriter('demo_two_sheets.xlsx') as writer: # 一次写出两个工作表(数据区上方留1个空行,模拟正文stores.xlsx的布局) demo_df.to_excel(writer, sheet_name='Sheet1', startrow=1) # Sheet1:自第2行起写 demo_df.to_excel(writer, sheet_name='Sheet2', startrow=1) # Sheet2:同样布局 print(pd.read_excel('demo_two_sheets.xlsx', sheet_name=0, skiprows=1)) # sheet_name=0按位置取第1张表;skiprows=1跳过空行后表头才正确 print(pd.read_excel('demo_two_sheets.xlsx', sheet_name=1, skiprows=1, nrows=2)) # sheet_name=1取第2张表;nrows=2只读前2行数据 print(pd.read_excel('demo_two_sheets.xlsx', sheet_name=0)) # 对照:不写skiprows时空行被当成表头,列名全变Unnamed预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
交易日 代码 收盘价 0 2024-01-01 600000.SH 10.5 1 2024-01-02 600036.SH 45.2 2 2024-01-03 601318.SH 55.8 3 2024-01-04 600519.SH 1700.0 交易日 代码 收盘价 0 2024-01-01 600000.SH 10.5 1 2024-01-02 600036.SH 45.2 Unnamed: 0 Unnamed: 1 Unnamed: 2 0 交易日 代码 收盘价 1 2024-01-01 00:00:00 600000.SH 10.5 2 2024-01-02 00:00:00 600036.SH 45.2 3 2024-01-03 00:00:00 601318.SH 55.8 4 2024-01-04 00:00:00 600519.SH 1700回扣本章:对应本章小结“要点”第 1 条与“易错点”第 2 条——
skiprows跳过数据区上方的行,nrows限制读取行数;说明行没跳对,读到的列名就不对。